oo::class create Folder {
    superclass Plugin
    variable PadWidget RenamedPadWidget Language MultiWidgets MultiYScroll

    constructor {multiYScroll language} {
        set PadWidget [set [info object namespace $multiYScroll]::PadWidget]
        set RenamedPadWidget _$PadWidget
        set Language $language
        set MultiYScroll $multiYScroll

        text ${PadWidget}Folder -bd 0 -bg #BDBDBD -width 1 -cursor arrow
        pack ${PadWidget}Folder -side left -fill y
        my scroll

        bind ${PadWidget}Folder <Button-1> [list + [self object] updateFolderWhenClick %x %y]

        foreach w $MultiWidgets {
            $w tag configure Hide -elide 1
        }

        _$PadWidget tag configure Underline -underline 1
        ${PadWidget}Folder tag configure CReverse -background #2E2E2E -foreground #F2F2F2
        ${PadWidget}Folder configure -state disable
    }

    method scroll {} {
        $MultiYScroll appendWidget ${PadWidget}Folder
        set MultiWidgets [set [info object namespace $MultiYScroll]::MultiWidgets]
    }

    method updateFolderWhenClick {x y} {
        set current [$RenamedPadWidget index @$x,$y]
        switch -- [${PadWidget}Folder get $current] {
            + {
                my withUnlock ${PadWidget}Folder {
                    my unfold $current
                }
            }
            - {
                my withUnlock ${PadWidget}Folder  {
                    my fold $current
                }
            }
        }
    }

    method fold {index} {
        ${PadWidget}Folder replace $index $index+1c + CReverse
        if [catch {set endId [my getFolderEnd "$index lineend"]}] {
            set endId [_$PadWidget index end]
        }
        foreach w $MultiWidgets {
            $w tag add Hide "$index +1l linestart" "$endId linestart"
        }
        _$PadWidget tag add Underline "$index linestart" "$index lineend"
    }

    method unfold {index} {
        ${PadWidget}Folder replace $index $index+1c - CReverse
        foreach w $MultiWidgets {
            foreach {from to} [$w tag nextrange Hide $index] {}
            if {[info exists from] && $from != {}} {
                $w tag remove Hide $from $to
            }
        }
        _$PadWidget tag remove Underline "$index linestart" "$index lineend"
    }

    method getFolderEnd {index} {
        return [$Language getBlockEnd _$PadWidget $index]
    }

    method getFolderHead {index} {
        return [$Language getBlockHead _$PadWidget $index]
    }

    method updateFolderBeforeInsert {index content args} {
        foreach tag [_$PadWidget tag names [list $index linestart]] {
            if {$tag == {Underline} && \
                    [string first \n $content] != -1 || \
                    [string first \r $content] != -1 } {
                my unfold $index
            }
        }
    }

    method updateFolderAfterInsert {index content args} {
        my withUnlock ${PadWidget}Folder {
            ${PadWidget}Folder insert "$index lineend" \
                [string repeat \n [expr int([_$PadWidget index end]) - int([${PadWidget}Folder index end])]]

            set i 0; set len [llength [split $content \r\n]]
            while {$i < $len} {
                my updateSign $index+${i}l
                incr i
            }
        }
    }

    method updateFolderBeforeDelete {index {to {}}} {
        if {$to == {}} { set to $index+1c }
        foreach tag [_$PadWidget tag names $index] {
            switch $tag {
                Hide {
                    my unfold [lindex [_$PadWidget tag prevrange Underline $index] 0]
                }
                Underline {
                    if {[catch {set headId [my getFolderHead $index]}] ||
                        $headId != {} && [_$PadWidget compare $headId >= $index] &&
                        [_$PadWidget compare $headId < $to]} {
                        my unfold $index
                    }
                }
            }
        }
    }

    method updateFolderAfterDelete {index {to {}}} {
        my withUnlock ${PadWidget}Folder {
            set gap [expr int([${PadWidget}Folder index end]) - int([_$PadWidget index end])]
            ${PadWidget}Folder delete "$index linestart" "$index +${gap}l lineend"
            my updateSign $index
        }
    }

    method updateFolderBeforeReplace {from to content args} {
        # NEW
        while {int($from) <= int($to)} {
            my updateFolderBeforeDelete $from
            set from [$RenamedPadWidget index "$from +1l"]
        }
    }

    method updateFolderAfterReplace {from to content args} {
        # NEW
        my updateFolderAfterInsert $from $content
    }

    method updateSign {index} {
        set index [_$PadWidget index $index]
        set content [$PadWidget get "$index linestart" "$index lineend"]
        set indices [$Language getPairIndices $content [list [$Language getIndentChar] [$Language getUnindentChar]]]
        if {$indices == {}} {
            ${PadWidget}Folder replace "$index linestart" "$index lineend" {}
        } elseif {[string index $content [lindex $indices end]] == [$Language getIndentChar]} {
            ${PadWidget}Folder replace "$index linestart" "$index lineend" - CReverse
        } elseif {[string index $content [lindex $indices 0]] == [$Language getUnindentChar]} {
            ${PadWidget}Folder replace "$index linestart" "$index lineend" e CReverse
        } else {
            ${PadWidget}Folder replace "$index linestart" "$index lineend" {}
        }
    }

    method getPluginEvents {} {
        return {
            {beforeInsert updateFolderBeforeInsert}
            {afterInsert updateFolderAfterInsert}
            {beforeDelete updateFolderBeforeDelete}
            {afterDelete updateFolderAfterDelete}
            {beforeReplace updateFolderBeforeReplace}
            {afterReplace updateFolderAfterReplace}
        }
    }

    destructor {
        catch {
            # MultiYScroll may have been destroyed for the reason of exit app
            $MultiYScroll removeWidget ${PadWidget}Folder
        }
        destroy ${PadWidget}Folder
    }
}